[fix] Fixed double encoding of device model labels in dashboard #651 #666
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe group_by loop in the admin dashboard now URL-quotes and appends the DB-derived label to chart Sequence Diagram(s)sequenceDiagram
participant User
participant AdminUI
participant Dashboard
participant DB
participant Template
User->>AdminUI: request dashboard / click chart
AdminUI->>Dashboard: get_dashboard_context()
Dashboard->>DB: query group-by labels
DB-->>Dashboard: raw label (e.g., "Dongwon T&I")
Dashboard->>Dashboard: filters.append(URL-quote label)
alt labels_i18n exists
Dashboard->>Dashboard: labels.append(i18n label)
else
Dashboard->>Dashboard: labels.append(HTML-escaped label)
end
Dashboard->>Template: context with filters and labels
Template->>User: rendered page / generated filter URL uses quoted value
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review SummaryStatus: 1 CRITICAL Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
ExplanationThe PR claims to fix issue #651 (double encoding of device model labels), but the fix is missing from the code. Current code (line 221): label = html.escape(label)This causes double-encoding when device model names contain Required to fix #651:
Files Reviewed
Reviewed by kimi-k2.5 · 563,346 tokens |
CI Failures: Formatting and Commit MessageHello @BHARATH0153,
Correct format example: |
26dc614 to
b9ebc2e
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@nemesifier please review when you are free |
80c0e65 to
540fe31
Compare
CI Failures SummaryHello @BHARATH0153,
|
Fixed issue where special characters (e.g. &) in project names were being double-encoded when used as dashboard chart filters. Closes openwisp#666
e09da99 to
eba06ed
Compare
…isp#666 Fixed special characters like & in project names being double-encoded when used as dashboard chart filters. Fixes openwisp#666
…isp#666 Fixed special characters like & in project names being double-encoded when used as dashboard chart filters. Fixes openwisp#666
eba06ed to
2326933
Compare
Fixed special characters like & in project names being double-encoded when used as dashboard chart filters. Fixes openwisp#666
2326933 to
19d03f8
Compare
|
@coderabbitai check the written new test in test_dashboard.py and review the changes is ready to merge |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 13389 🏁 Script executed: Length of output: 10706
✅ What the Test Does Well
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me pull up the actual code to review it thoroughly. 🏁 Script executed: Length of output: 24143
✅ Fix in
|
| Assertion | Purpose |
|---|---|
assertIn("Dongwon T&I", filters) |
Raw label is used for URL filter |
assertNotIn("Dongwon T&I", filters) |
HTML-encoded string is NOT in filter |
assertIn("Dongwon T&I", labels) |
Escaped label is shown in UI (XSS safe) |
Minor suggestions (non-blocking)
-
Failure message on
assertIsNotNone: If the chart name ever changes, the test will fail with a crypticNone is not Nonemessage. Consider:self.assertIsNotNone( target_chart, "Chart 'Operator Project Distribution' not found in dashboard_charts" )
-
Chart name coupling: The test depends on the hardcoded string
"Operator Project Distribution". This is acceptable since it mirrors how the chart is registered, but worth noting as a maintenance point.
Overall verdict
The fix is correct, the test covers the bug precisely, XSS protection is preserved (test_get_dashboard_context_html_escape still passes), and no regressions are introduced. The PR is ready to merge pending maintainer sign-off. 🎉
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/test_project/tests/test_dashboard.py (1)
275-316: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winExtend
test_get_dashboard_context_html_escapeto assertfiltersholds the raw (unescaped) XSS payload.The new test correctly validates the double-encoding fix. However, now that
filtersstores raw DB values (not HTML-escaped ones),test_get_dashboard_context_html_escapeonly tests thatlabelsare escaped—it doesn't assert thatfilterscontains the raw malicious value. This leaves a gap: if a future change accidentally re-escapesfilters, only the URL test would catch it, not the XSS test.Consider adding to
test_get_dashboard_context_html_escape:✅ Proposed addition to
test_get_dashboard_context_html_escape# ensure DB value is escaped self.assertEqual( context["dashboard_charts"][0]["query_params"]["labels"][0], "<script>alert(1)</script>", ) + # ensure filters keep the raw value (not double-escaped) for correct URL generation + self.assertIn( + "<script>alert(1)</script>", + context["dashboard_charts"][0].get("filters", []), + )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_project/tests/test_dashboard.py` around lines 275 - 316, Extend test_get_dashboard_context_html_escape to also assert that filters contain the raw DB value (unescaped) so we don't regress to double-encoding: after calling get_dashboard_context(mocked_request), fetch filters from context["dashboard_charts"][0]["filters"] (the same chart used for labels) and add assertions that the raw string "<script>alert(1)</script>" is present in filters and that its escaped form "<script>alert(1)</script>" is not; keep the existing label assertions intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@tests/test_project/tests/test_dashboard.py`:
- Around line 275-316: Extend test_get_dashboard_context_html_escape to also
assert that filters contain the raw DB value (unescaped) so we don't regress to
double-encoding: after calling get_dashboard_context(mocked_request), fetch
filters from context["dashboard_charts"][0]["filters"] (the same chart used for
labels) and add assertions that the raw string "<script>alert(1)</script>" is
present in filters and that its escaped form
"<script>alert(1)</script>" is not; keep the existing label
assertions intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b7c3a9f7-2e15-4168-998f-7fcd29bd8d55
📒 Files selected for processing (2)
openwisp_utils/admin_theme/dashboard.pytests/test_project/tests/test_dashboard.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{py,html,txt}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
tests/test_project/tests/test_dashboard.pyopenwisp_utils/admin_theme/dashboard.py
🔇 Additional comments (1)
openwisp_utils/admin_theme/dashboard.py (1)
203-231: ⚡ Quick winNo XSS vulnerability exists. The
filtersarray is correctly stored with raw (unescaped) DB values, as confirmed by the testtest_filter_not_double_encoded, which asserts filters contain unescaped values like"Dongwon T&I".The template renders
dashboard_chartsas a JavaScript object literal inside a<script>tag using{{ dashboard_charts | safe }}, meaningfiltersvalues are embedded as strings within JSON, not parsed as HTML. Inow-dashboard.js, filters are only used to concatenate URL query parameters (path += filters[i]), where they are treated as URL-safe strings, not HTML.The change is correct and safe.
> Likely an incorrect or invalid review comment.
895fec7 to
f5ba670
Compare
|
@coderabbitai is it ready to merge now after maintainer review |
|
Tip For best results, initiate chat on the files or code changes.
From a technical standpoint — Yes, the PR is ready. ✅
From a process standpoint — it still needs maintainer sign-off. ⏳ Per OpenWISP's contributing guidelines, a PR will be merged only once the CI build completes successfully and after project maintainers have reviewed and tested it. Based on the comment chain, The decision to merge belongs exclusively to the project maintainers. I'd suggest pinging
There's nothing more to change on your end — the ball is in the maintainers' court. 🎉 |
|
@nemesifier |
f5ba670 to
c7f8dd8
Compare
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
@coderabbitai check overall improvement and review and is it ready to merge and check fulfillment of @nemesifier changes or comment |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 17420
🚫 Blocking Issues from
|
| Issue | Status |
|---|---|
filters.append(label) hoisted above if/else |
❌ Not done |
encodeURIComponent in JS (or server-side pre-encoding) |
❌ Not done |
| Test covering the final URL output | ❌ Not done |
| CI passing |
Please address these two code changes and extend the test before requesting another review from @nemesifier.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 17811
|
| Assertion | Purpose |
|---|---|
assertIn("Dongwon%20T%26I", filters) |
Percent-encoded value is in filters ✅ |
assertNotIn("Dongwon T&I", filters) |
Raw/unencoded value is absent ✅ |
assertIn("Dongwon T&I", labels) |
Display label is HTML-escaped ✅ |
assertIn("%26", final_url) |
Final URL has & properly encoded ✅ |
assertNotIn("&I", final_url.split("=",1)[-1]) |
No broken query param split ✅ |
Overall status
| Check | Status |
|---|---|
@nemesifier point 1 (hoist filters.append) |
✅ Addressed |
@nemesifier point 2 (URL encoding for JS) |
✅ Addressed with urllib.parse.quote |
| XSS protection preserved | ✅ html.escape still applied to labels |
| Test covers the fix end-to-end | ✅ |
| CI | ⏳ Awaiting green (transient failure was auto-restarted) |
The PR is technically ready. @nemesifier's review is still marked as CHANGES_REQUESTED because they haven't re-reviewed the latest commits yet. Please ping them directly to let them know both of their concerns have been addressed and the PR is ready for a second look.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_project/tests/test_dashboard.py`:
- Around line 321-323: Replace the brittle substring checks around final_url
(constructed from target_chart["target_link"] + filters[filter_index]) with
proper URL parsing: use urllib.parse.urlparse on final_url, extract the query,
use urllib.parse.parse_qs (or parse_qsl) to find the specific query parameter
value you care about, then use urllib.parse.unquote or unquote_plus to assert
the decoded parameter does not contain the raw "&I" sequence and assert the raw
encoded query contains "%26" where expected; update the assertions that
reference final_url, target_chart["target_link"], and filters[filter_index] to
use these parsed/decoded values instead of split/substring checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 693e3506-9c3c-4101-b75d-607ab3e1ca95
📒 Files selected for processing (2)
openwisp_utils/admin_theme/dashboard.pytests/test_project/tests/test_dashboard.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{py,html,txt}
📄 CodeRabbit inference engine (Custom checks)
For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
tests/test_project/tests/test_dashboard.pyopenwisp_utils/admin_theme/dashboard.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Runopenwisp-qa-formatto reformat Python and adjacent files in place before considering a change complete
Prioritize comments and docstrings that explain why the code is shaped this way (constraints, trade-offs, references to issues) rather than translating code into prose
Files:
tests/test_project/tests/test_dashboard.pyopenwisp_utils/admin_theme/dashboard.py
**/*.{py,css,js,txt}
📄 CodeRabbit inference engine (AGENTS.md)
Run
./run-qa-checkswhich executesopenwisp-qa-checkwith project linter configuration for CSS, JS, and migration paths, and treat any failure as blocking
Files:
tests/test_project/tests/test_dashboard.pyopenwisp_utils/admin_theme/dashboard.py
**/*test*.py
📄 CodeRabbit inference engine (AGENTS.md)
When fixing a bug, write a failing test first that reproduces the bug before implementing the fix, then confirm the test passes after the fix
Files:
tests/test_project/tests/test_dashboard.py
**/tests/test_*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/tests/test_*.py: For subprocess-based tests, prefer adding in-process unit tests that import the module and call its public methods directly instead of relying solely on subprocess CLI invocation, to ensure coverage instrumentation captures the code
Before declaring a test suite complete, run pytest with--covagainst the file under test and confirm the coverage report reflects what you expect, checking for subprocess-only tests that report ~0% coverage with most of the file under 'Missing'
Files:
tests/test_project/tests/test_dashboard.py
🔇 Additional comments (3)
openwisp_utils/admin_theme/dashboard.py (2)
213-220: Good separation of URL value vs display label.This correctly keeps
filtersURL-safe while preserving HTML-escaped labels for rendering, which addresses the double-encoding/XSS split cleanly.
213-213: JavaScript correctly handles pre-encoded filters without re-encoding.The frontend contract matches the server-side change. At line 119 of
ow-dashboard.js, pre-encoded filters are appended directly to the path withoutencodeURIComponent(). The fallback at line 121 only encodes the label when a filter is unavailable, preventing any double-encoding. The implementation is correct.tests/test_project/tests/test_dashboard.py (1)
298-324: Nice regression coverage for encoding vs escaping.This test captures the intended behavior split (
filtersURL-encoded,labelsHTML-escaped) and protects against the original bug.
Code Style and Test FailuresHello @BHARATH0153, There are multiple failures in the CI log:
Please address these issues and push a new commit. |
…p#651 Fixed the double-encoding issue in admin dashboard charts where device models with special characters (like &) generated malformed URLs. Updated get_dashboard_context to store raw strings in the filters array for correct JavaScript URL generation, and applied mark_safe with conditional_escape exclusively to UI labels to maintain XSS security. Fixes openwisp#651
fbc5211 to
7161e77
Compare
|
@coderabbitai now is it ready for merge check new commit |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 6229
✅ CI — Fully GreenAll 14 Python/Django matrix combinations completed with
Coveralls also reports ✅ Code — Both of
|
| Assertion | Purpose |
|---|---|
assertIn("Dongwon%20T%26I", filters) |
URL-encoded value in filters ✅ |
assertNotIn("Dongwon T&I", filters) |
Raw unencoded value absent ✅ |
assertIn("Dongwon T&I", labels) |
HTML-escaped label for display ✅ |
assertIn("%26", final_url) |
Final URL correctly encoded ✅ |
assertNotIn("&I", final_url.split("=",1)[-1]) |
No broken query param split ✅ |
⏳ Only Remaining Blocker — @nemesifier Re-review
The PR is MERGEABLE with no conflicts, but @nemesifier's CHANGES_REQUESTED review (2026-05-09) has not been updated — they haven't re-reviewed the latest commits yet.
There is one unresolved nitpick from me about using urllib.parse.parse_qs for stronger URL assertions in the test, but it is explicitly marked as 🔵 Trivial / non-blocking.
The ball is in @nemesifier's court. Everything requested has been implemented correctly. Please ping them one more time to let them know the PR is ready for a second look.

Checklist
I have read the OpenWISP Contributing Guidelines.
I have manually tested the changes proposed in this pull request.
I have written new test cases for new code and/or updated existing tests for changes to existing code.
I have updated the documentation.
Reference to Existing Issue
Fixes #651
Description of Changes
Hoisted filters.append Moved the append logic above the if/else block to remove the duplication and keep the code DRY.
Server-side URL Encoding Wrapped the label in quote(label, safe="") from urllib.parse before appending it to filters. This ensures the JS click handler receives a fully URL-encoded string (e.g., %26 instead of &), preventing the URL parsing issue in the browser.
Local validation:
black openwisp_utils/admin_theme/dashboard.py tests/test_project/tests/test_dashboard.py
python runtests.py test_project.tests.test_dashboard.TestAdminDashboard.test_filter_not_double_encoded -v 2
python runtests.py test_project.tests.test_dashboard.TestAdminDashboard.test_index_content -v 2
Screenshot